<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CyberSerial - Web + Python 串口通信平台</title>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        :root {
            --primary: #6366f1;
            --primary-dark: #4f46e5;
            --secondary: #10b981;
            --accent: #f59e0b;
            --danger: #ef4444;
            --dark: #1f2937;
            --darker: #111827;
            --light: #f8fafc;
            --gray: #6b7280;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, var(--darker), #1e1b4b);
            color: var(--light);
            min-height: 100vh;
            overflow-x: hidden;
        }

        .container {
            max-width: 1400px;
            margin: 0 auto;
            padding: 20px;
        }

        .header {
            text-align: center;
            margin-bottom: 30px;
            position: relative;
        }

        .header h1 {
            font-size: 3rem;
            font-weight: 800;
            background: linear-gradient(135deg, var(--primary), var(--secondary));
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            margin-bottom: 10px;
            animation: glow 2s ease-in-out infinite alternate;
        }

        .header .subtitle {
            color: var(--gray);
            font-size: 1.2rem;
        }

        .tech-badge {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            background: rgba(99, 102, 241, 0.2);
            padding: 8px 16px;
            border-radius: 20px;
            margin: 10px 5px;
            border: 1px solid var(--primary);
            backdrop-filter: blur(10px);
        }

        .dashboard {
            display: grid;
            grid-template-columns: 300px 1fr;
            gap: 20px;
            margin-bottom: 20px;
        }

        .control-panel {
            background: rgba(31, 41, 55, 0.8);
            border-radius: 15px;
            padding: 25px;
            border: 1px solid rgba(99, 102, 241, 0.3);
            backdrop-filter: blur(10px);
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
        }

        .stats-panel {
            background: rgba(31, 41, 55, 0.8);
            border-radius: 15px;
            padding: 25px;
            border: 1px solid rgba(16, 185, 129, 0.3);
            backdrop-filter: blur(10px);
        }

        .data-panel {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
            height: 500px;
        }

        .panel {
            background: rgba(31, 41, 55, 0.8);
            border-radius: 15px;
            padding: 25px;
            border: 1px solid rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(10px);
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
        }

        .panel-title {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-bottom: 20px;
            font-size: 1.3rem;
            color: var(--primary);
        }

        .form-group {
            margin-bottom: 20px;
        }

        label {
            display: block;
            margin-bottom: 8px;
            color: var(--gray);
            font-weight: 500;
        }

        select, textarea {
            width: 100%;
            padding: 12px;
            border: 1px solid rgba(99, 102, 241, 0.3);
            border-radius: 8px;
            background: rgba(17, 24, 39, 0.8);
            color: var(--light);
            font-size: 14px;
            transition: all 0.3s ease;
        }

        select:focus, textarea:focus {
            outline: none;
            border-color: var(--primary);
            box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
        }

        .btn {
            padding: 12px 24px;
            border: none;
            border-radius: 8px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            display: inline-flex;
            align-items: center;
            gap: 8px;
            font-size: 14px;
        }

        .btn-primary {
            background: linear-gradient(135deg, var(--primary), var(--primary-dark));
            color: white;
        }

        .btn-primary:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 25px rgba(99, 102, 241, 0.4);
        }

        .btn-success {
            background: linear-gradient(135deg, var(--secondary), #059669);
            color: white;
        }

        .btn-danger {
            background: linear-gradient(135deg, var(--danger), #dc2626);
            color: white;
        }

        .btn-group {
            display: flex;
            gap: 10px;
            margin-top: 15px;
        }

        .status-indicator {
            display: flex;
            align-items: center;
            gap: 10px;
            padding: 15px;
            border-radius: 10px;
            margin-top: 20px;
            background: rgba(239, 68, 68, 0.2);
            border: 1px solid var(--danger);
            transition: all 0.3s ease;
        }

        .status-indicator.connected {
            background: rgba(16, 185, 129, 0.2);
            border: 1px solid var(--secondary);
        }

        .pulse {
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: var(--danger);
            animation: pulse 2s infinite;
        }

        .status-indicator.connected .pulse {
            background: var(--secondary);
        }

        .stats-grid {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 15px;
            margin-top: 20px;
        }

        .stat-card {
            background: rgba(17, 24, 39, 0.6);
            padding: 20px;
            border-radius: 10px;
            text-align: center;
            border: 1px solid rgba(255, 255, 255, 0.1);
            transition: transform 0.3s ease;
        }

        .stat-card:hover {
            transform: translateY(-5px);
        }

        .stat-number {
            font-size: 2rem;
            font-weight: 800;
            background: linear-gradient(135deg, var(--primary), var(--secondary));
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
        }

        .stat-label {
            color: var(--gray);
            font-size: 0.9rem;
            margin-top: 5px;
        }

        #receiveData {
            height: 400px;
            background: rgba(17, 24, 39, 0.8);
            border: 1px solid rgba(99, 102, 241, 0.3);
            border-radius: 8px;
            padding: 15px;
            overflow-y: auto;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            line-height: 1.4;
        }

        .data-entry {
            margin-bottom: 8px;
            padding: 8px;
            border-radius: 5px;
            background: rgba(99, 102, 241, 0.1);
            border-left: 3px solid var(--primary);
            animation: slideIn 0.3s ease;
        }

        .data-time {
            color: var(--accent);
            font-weight: 600;
        }

        .data-content {
            color: var(--light);
            word-break: break-all;
        }

        .data-size {
            color: var(--secondary);
            font-size: 0.8rem;
            margin-left: 10px;
        }

        .controls {
            display: flex;
            gap: 10px;
            margin-top: 15px;
            flex-wrap: wrap;
        }

        .checkbox-group {
            display: flex;
            align-items: center;
            gap: 8px;
            margin: 10px 0;
        }

        @keyframes glow {
            from {
                text-shadow: 0 0 20px rgba(99, 102, 241, 0.5);
            }
            to {
                text-shadow: 0 0 30px rgba(99, 102, 241, 0.8), 0 0 40px rgba(99, 102, 241, 0.6);
            }
        }

        @keyframes pulse {
            0% {
                transform: scale(0.95);
                box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);
            }
            70% {
                transform: scale(1);
                box-shadow: 0 0 0 10px rgba(239, 68, 68, 0);
            }
            100% {
                transform: scale(0.95);
                box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);
            }
        }

        @keyframes slideIn {
            from {
                opacity: 0;
                transform: translateX(-20px);
            }
            to {
                opacity: 1;
                transform: translateX(0);
            }
        }

        .floating-tech {
            position: absolute;
            font-size: 3rem;
            opacity: 0.1;
            animation: float 6s ease-in-out infinite;
        }

        .floating-tech.python {
            top: 10%;
            left: 5%;
            color: var(--secondary);
        }

        .floating-tech.web {
            top: 20%;
            right: 5%;
            color: var(--primary);
            animation-delay: 2s;
        }

        .floating-tech.serial {
            bottom: 10%;
            left: 10%;
            color: var(--accent);
            animation-delay: 4s;
        }

        @keyframes float {
            0%, 100% {
                transform: translateY(0px) rotate(0deg);
            }
            50% {
                transform: translateY(-20px) rotate(5deg);
            }
        }

        /* 滚动条样式 */
        ::-webkit-scrollbar {
            width: 8px;
        }

        ::-webkit-scrollbar-track {
            background: rgba(31, 41, 55, 0.5);
            border-radius: 4px;
        }

        ::-webkit-scrollbar-thumb {
            background: linear-gradient(135deg, var(--primary), var(--secondary));
            border-radius: 4px;
        }

        ::-webkit-scrollbar-thumb:hover {
            background: linear-gradient(135deg, var(--primary-dark), #059669);
        }
    </style>
</head>
<body>
    <div class="floating-tech python">
        <i class="fab fa-python"></i>
    </div>
    <div class="floating-tech web">
        <i class="fab fa-html5"></i>
    </div>
    <div class="floating-tech serial">
        <i class="fas fa-microchip"></i>
    </div>

    <div class="container">
        <div class="header">
            <h1>CyberSerial</h1>
            <div class="subtitle">Web + Python 超融合串口通信平台</div>
            <div>
                <span class="tech-badge">
                    <i class="fab fa-python"></i> Python Backend
                </span>
                <span class="tech-badge">
                    <i class="fab fa-html5"></i> Web Frontend
                </span>
                <span class="tech-badge">
                    <i class="fas fa-bolt"></i> Real-time Communication
                </span>
            </div>
        </div>

        <div class="dashboard">
            <div class="control-panel">
                <div class="panel-title">
                    <i class="fas fa-sliders-h"></i>
                    <span>串口控制</span>
                </div>
                
                <div class="form-group">
                    <label><i class="fas fa-usb"></i> 选择串口</label>
                    <select id="portSelect">
                        <option value="">选择串口设备...</option>
                    </select>
                </div>

                <div class="form-group">
                    <label><i class="fas fa-tachometer-alt"></i> 波特率</label>
                    <select id="baudrateSelect">
                        <option value="9600">9600</option>
                        <option value="19200">19200</option>
                        <option value="38400">38400</option>
                        <option value="57600">57600</option>
                        <option value="115200">115200</option>
                    </select>
                </div>

                <div class="btn-group">
                    <button class="btn btn-primary" onclick="connectSerial()">
                        <i class="fas fa-plug"></i> 连接串口
                    </button>
                    <button class="btn btn-danger" onclick="disconnectSerial()">
                        <i class="fas fa-power-off"></i> 断开连接
                    </button>
                </div>

                <button class="btn btn-success" onclick="refreshPorts()" style="width: 100%; margin-top: 10px;">
                    <i class="fas fa-sync-alt"></i> 刷新设备列表
                </button>

                <div id="status" class="status-indicator">
                    <div class="pulse"></div>
                    <span>串口未连接</span>
                </div>
            </div>

            <div class="stats-panel">
                <div class="panel-title">
                    <i class="fas fa-chart-bar"></i>
                    <span>通信统计</span>
                </div>
                <div class="stats-grid">
                    <div class="stat-card">
                        <div class="stat-number" id="receivedCount">0</div>
                        <div class="stat-label">接收数据包</div>
                    </div>
                    <div class="stat-card">
                        <div class="stat-number" id="sentCount">0</div>
                        <div class="stat-label">发送数据包</div>
                    </div>
                    <div class="stat-card">
                        <div class="stat-number" id="dataRate">0/s</div>
                        <div class="stat-label">实时速率</div>
                    </div>
                    <div class="stat-card">
                        <div class="stat-number" id="uptime">00:00</div>
                        <div class="stat-label">运行时间</div>
                    </div>
                </div>
            </div>
        </div>

        <div class="data-panel">
            <div class="panel">
                <div class="panel-title">
                    <i class="fas fa-paper-plane"></i>
                    <span>数据发送</span>
                </div>
                <textarea id="sendData" placeholder="输入要发送的数据，支持文本和HEX格式..." rows="8"></textarea>
                <div class="controls">
                    <button class="btn btn-primary" onclick="sendData()">
                        <i class="fas fa-paper-plane"></i> 发送文本
                    </button>
                    <button class="btn btn-success" onclick="sendHexData()">
                        <i class="fas fa-code"></i> 发送HEX
                    </button>
                    <button class="btn" onclick="clearSend()" style="background: var(--gray); color: white;">
                        <i class="fas fa-eraser"></i> 清空
                    </button>
                </div>
            </div>

            <div class="panel">
                <div class="panel-title">
                    <i class="fas fa-signal"></i>
                    <span>数据接收</span>
                    <button class="btn" onclick="clearReceive()" style="background: var(--gray); color: white; margin-left: auto;">
                        <i class="fas fa-trash"></i> 清空记录
                    </button>
                </div>
                <div id="receiveData"></div>
                <div class="controls">
                    <div class="checkbox-group">
                        <input type="checkbox" id="autoScroll" checked>
                        <label for="autoScroll">自动滚动</label>
                    </div>
                    <div class="checkbox-group">
                        <input type="checkbox" id="hexDisplay">
                        <label for="hexDisplay">HEX显示</label>
                    </div>
                    <div class="checkbox-group">
                        <input type="checkbox" id="timestamp" checked>
                        <label for="timestamp">显示时间戳</label>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script>
        let startTime = Date.now();
        let lastUpdate = Date.now();
        let packetCount = 0;

        // 页面加载时获取可用串口
        window.onload = function() {
            refreshPorts();
            updateUptime();
            setInterval(updateUptime, 1000);
        }

        // 更新运行时间
        function updateUptime() {
            const elapsed = Math.floor((Date.now() - startTime) / 1000);
            const minutes = Math.floor(elapsed / 60);
            const seconds = elapsed % 60;
            document.getElementById('uptime').textContent = 
                `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
        }

        // 刷新串口列表
        async function refreshPorts() {
            try {
                const ports = await pywebview.api.get_available_ports();
                const select = document.getElementById('portSelect');
                select.innerHTML = '<option value="">选择串口设备...</option>';
                ports.forEach(port => {
                    const option = document.createElement('option');
                    option.value = port.port;
                    option.textContent = `${port.port} - ${port.description}`;
                    select.appendChild(option);
                });
            } catch (error) {
                console.error('获取串口列表失败:', error);
            }
        }

        // 连接串口
        async function connectSerial() {
            const port = document.getElementById('portSelect').value;
            const baudrate = document.getElementById('baudrateSelect').value;
            
            if (!port) {
                alert('请选择串口设备');
                return;
            }

            const result = await pywebview.api.connect_serial(port, parseInt(baudrate));
            if (result.success) {
                const status = document.getElementById('status');
                status.innerHTML = '<div class="pulse"></div><span>已连接: ' + port + '</span>';
                status.className = 'status-indicator connected';
            } else {
                alert(result.message);
            }
        }

        // 断开串口
        async function disconnectSerial() {
            const result = await pywebview.api.disconnect_serial();
            const status = document.getElementById('status');
            status.innerHTML = '<div class="pulse"></div><span>串口未连接</span>';
            status.className = 'status-indicator';
        }

        // 发送数据
        async function sendData() {
            const data = document.getElementById('sendData').value;
            if (data) {
                const result = await pywebview.api.send_data(data);
                if (!result.success) {
                    alert(result.message);
                }
            }
        }

        // 发送HEX数据
        async function sendHexData() {
            const hexData = document.getElementById('sendData').value.replace(/\\s/g, '');
            if (hexData) {
                try {
                    const bytes = new Uint8Array(hexData.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
                    const result = await pywebview.api.send_data(bytes);
                    if (!result.success) {
                        alert(result.message);
                    }
                } catch (e) {
                    alert('HEX格式错误，请确保输入有效的十六进制数据');
                }
            }
        }

        // 清空发送区
        function clearSend() {
            document.getElementById('sendData').value = '';
        }

        // 清空接收区
        function clearReceive() {
            document.getElementById('receiveData').innerHTML = '';
        }

        // 更新统计信息
        function updateStats(received, sent) {
            document.getElementById('receivedCount').textContent = received;
            document.getElementById('sentCount').textContent = sent;
            
            // 计算实时速率
            const now = Date.now();
            const elapsed = (now - lastUpdate) / 1000;
            if (elapsed >= 1) {
                const rate = Math.round(packetCount / elapsed);
                document.getElementById('dataRate').textContent = rate + '/s';
                packetCount = 0;
                lastUpdate = now;
            }
        }

        // 添加串口数据到显示区域
        function addSerialData(timestamp, data, length) {
            packetCount++;
            
            const receiveDiv = document.getElementById('receiveData');
            const hexDisplay = document.getElementById('hexDisplay').checked;
            const showTimestamp = document.getElementById('timestamp').checked;
            
            let displayData = data;
            if (!hexDisplay) {
                try {
                    displayData = hexToString(data);
                } catch (e) {
                    displayData = data;
                }
            }
            
            const dataEntry = document.createElement('div');
            dataEntry.className = 'data-entry';
            
            let content = '';
            if (showTimestamp) {
                content += `<span class="data-time">[${timestamp}]</span> `;
            }
            content += `<span class="data-content">${displayData}</span>`;
            content += `<span class="data-size">${length} bytes</span>`;
            
            dataEntry.innerHTML = content;
            receiveDiv.appendChild(dataEntry);
            
            // 自动滚动
            if (document.getElementById('autoScroll').checked) {
                receiveDiv.scrollTop = receiveDiv.scrollHeight;
            }
        }

        // HEX转字符串工具函数
        function hexToString(hex) {
            let str = '';
            for (let i = 0; i < hex.length; i += 2) {
                const byte = parseInt(hex.substr(i, 2), 16);
                if (byte >= 32 && byte <= 126) {
                    str += String.fromCharCode(byte);
                } else {
                    str += `\\\\x${byte.toString(16).padStart(2, '0')}`;
                }
            }
            return str;
        }
    </script>
</body>
</html>